import deselectCurrent from "toggle-selection";

const clipboardToIE11Formatting = {
  "text/plain": "Text",
  "text/html": "Url",
  "image/png": "Image",
};
const { clipboardData: winClipboardData } = window as any;

interface ICopyOptions {
  format?: keyof typeof clipboardToIE11Formatting;
}
const DEF_OPTIONS: ICopyOptions = {
  format: "text/plain",
};

//! 代码参考来源于 copy-to-clipboard
// git 地址：https://github.com/sudodoki/copy-to-clipboard
async function copyTextOrHtml(data: string, options: ICopyOptions) {
  return new Promise((resolve, reject) => {
    let range, selection, mark, reselectPrevious;

    try {
      reselectPrevious = deselectCurrent();

      range = document.createRange();
      selection = document.getSelection();

      mark = document.createElement("span");
      mark.textContent = data;
      // avoid screen readers from reading out loud the text
      mark.ariaHidden = "true";
      // reset user styles for span element
      mark.style.all = "unset";
      // prevents scrolling to the end of the page
      mark.style.position = "fixed";
      mark.style.top = 0;
      mark.style.clip = "rect(0, 0, 0, 0)";
      // used to preserve spaces and line breaks
      mark.style.whiteSpace = "pre";
      // do not inherit user-select (it may be `none`)
      mark.style.webkitUserSelect = "text";
      mark.style.MozUserSelect = "text";
      mark.style.msUserSelect = "text";
      mark.style.userSelect = "text";
      mark.addEventListener("copy", e => {
        e.preventDefault();
        e.stopPropagation();
        if (options.format) {
          if (typeof e.clipboardData === "undefined") {
            // IE 11
            // 无法使用 e.clipboardData 的方式进行复制操作
            // 尝试 IE 的复制处理操作
            winClipboardData.clearData();
            const format = clipboardToIE11Formatting[options.format];
            winClipboardData.setData(format, data);
          } else {
            // 所有其他浏览器基本都支持此方式进行复制操作
            e.clipboardData.clearData();
            e.clipboardData.setData(options.format, data);
          }
        }
        resolve(e.clipboardData);
      });

      document.body.appendChild(mark);

      range.selectNodeContents(mark);
      selection.addRange(range);

      const successful = document.execCommand("copy"); // 触发 mark 的 copy 事件
      if (!successful) {
        throw new Error("指令复制的操作失败了");
      }
    } catch (err) {
      // 尝试使用 IE 的复制指令方式
      try {
        const format = clipboardToIE11Formatting[options.format];
        winClipboardData.setData(format, data);
        resolve(winClipboardData);
      } catch (err) {
        // IE 的方式进行复制也发生了异常时候的处理，此时可能完全不支持复制的用法
        reject("当前环境可能不支持复制的操作");
      }
    } finally {
      // 释放资源
      if (selection) {
        if (typeof selection.removeRange === "function") {
          selection.removeRange(range);
        } else {
          selection.removeAllRanges();
        }
      }

      if (mark) {
        document.body.removeChild(mark);
      }
      reselectPrevious();
    }
  });
}

async function copyImage(data: Blob) {
  const item = new ClipboardItem({
    "image/png": data,
  });
  await navigator.clipboard.write([item]);
}

async function copyHtmlElement(el: HTMLElement, options: ICopyOptions) {
  return new Promise((resolve, reject) => {
    const mark = el;
    const data = options.format === "text/plain" ? el.innerText : el.innerHTML;
    let range, styles, selection, reselectPrevious;
    const copyHandler = e => {
      e.preventDefault();
      e.stopPropagation();
      if (options.format) {
        if (typeof e.clipboardData === "undefined") {
          // IE 11
          // 无法使用 e.clipboardData 的方式进行复制操作
          // 尝试 IE 的复制处理操作
          winClipboardData.clearData();
          const format = clipboardToIE11Formatting[options.format];
          winClipboardData.setData(format, data);
        } else {
          // 所有其他浏览器基本都支持此方式进行复制操作
          e.clipboardData.clearData();
          e.clipboardData.setData(options.format, data);
        }
      }
      resolve(e.clipboardData);
    };
    try {
      reselectPrevious = deselectCurrent();

      range = document.createRange();
      selection = document.getSelection();

      styles = {
        all: mark.style.all,
        whiteSpace: mark.style.whiteSpace,
        webkitUserSelect: mark.style.webkitUserSelect,
        // MozUserSelect:mark.style.MozUserSelect,
        // msUserSelect:mark.style.msUserSelect,
        userSelect: mark.style.userSelect,
      };

      //TODO: 将来可以进行优化复制动作，实现所见即所得的复制
      // const style2 = window.getComputedStyle(mark);
      // console.log(style2);
      mark.style.all = "unset";
      mark.style.whiteSpace = "pre";
      mark.style.webkitUserSelect = "text";
      // mark.style.MozUserSelect = "text";
      // mark.style.msUserSelect = "text";
      mark.style.userSelect = "text";
      mark.addEventListener("copy", copyHandler);

      range.selectNodeContents(mark);
      selection.addRange(range);

      const successful = document.execCommand("copy"); // 触发 mark 的 copy 事件
      if (!successful) {
        throw new Error("指令复制的操作失败了");
      }
    } catch (err) {
      // 尝试使用 IE 的复制指令方式
      try {
        const format = clipboardToIE11Formatting[options.format];
        winClipboardData.setData(format, data);
        resolve(winClipboardData);
      } catch (err) {
        // IE 的方式进行复制也发生了异常时候的处理，此时可能完全不支持复制的用法
        reject("当前环境可能不支持复制的操作");
      }
    } finally {
      // 释放资源
      if (selection) {
        if (typeof selection.removeRange === "function") {
          selection.removeRange(range);
        } else {
          selection.removeAllRanges();
        }
      }
      mark.removeEventListener("copy", copyHandler);

      mark.style.all = styles.all;
      mark.style.whiteSpace = styles.whiteSpace;
      mark.style.webkitUserSelect = styles.webkitUserSelect;
      // mark.style.MozUserSelect = styles.MozUserSelect;
      // mark.style.msUserSelect = styles.msUserSelect;
      mark.style.userSelect = styles.userSelect;

      reselectPrevious();
    }
  });
}

export async function copy(data: string | Blob | HTMLElement, options: ICopyOptions = {}) {
  const opts = { ...DEF_OPTIONS, ...options };
  if (data instanceof HTMLElement) {
    if (["text/plain", "text/html"].includes(opts.format)) {
      await copyHtmlElement(data, opts);
    } else {
      throw new Error("要复制的数据格式不正确");
    }
  } else if (data instanceof Blob) {
    if (opts.format === "image/png") {
      // 复制图片
      await copyImage(data as Blob);
    } else {
      throw new Error("要复制的数据格式不正确");
    }
  } else if (typeof data === "string") {
    if (["text/plain", "text/html"].includes(opts.format)) {
      // 复制文字类的内容时的处理
      await copyTextOrHtml(data as string, opts);
    } else {
      throw new Error("要复制的数据格式不正确");
    }
  }
}
